/*
*epoll模型编程案例
*
*1、处理客户端异常断开和正常断开，关闭通信套接字
*2、监控多种事件时，判断那种事件发生的方法并做相应的处理
*3、处理退出的客户端
*/




#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define MAX_LINE 255
#define OPEN_MAX 255
#define LISTENQ 255
#define SERV_PORT 255
#define INFTIM 255
#define TIMEOUT_MS 255
#define EVENT_MAX_COUNT 255
#define EPOLL_SIZE 1024
void set_nonblocking(int sock);

int main(int argc,const char* argv[])
{
	int i, maxi, listenfd, connfd, sockfd, epfd, nfds, port_number;
	ssize_t n;
	char line_buf[MAX_LINE];

	if (2 == argc) {
		if ((port_number = atoi(argv[1])) < 0)	{
			fprintf(stderr, "Usage:%s portnumber\n", argv[0]);
			return 0;
		}
	} else {
		fprintf(stderr, "Usage:%s portnumber\n", argv[0]);
		return 0;
	}

	// ev用于注册事件，event_list用于回传要处理的事件
	struct epoll_event ev, event_list[EVENT_MAX_COUNT];
	
	epfd = epoll_create(EPOLL_SIZE); // 创建epoll池
	
	struct sockaddr_in client_addr;
	socklen_t client_addr_len;
	struct sockaddr_in server_addr;

	listenfd = socket(AF_INET, SOCK_STREAM, 0);	// 创建socket
	if (listenfd < 0) {
		perror("socket");
		return -1;
	}

	//set_nonblocking(listenfd);
	ev.data.fd = listenfd;
	ev.events = EPOLLIN | EPOLLET; // 事件类型，有可读取数据或状态变化

	epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); // 添加需要监测fd, 并注册事件

	// 绑定IP
	bzero(&server_addr, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	server_addr.sin_port = htons(port_number);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
	if (0 != bind(listenfd, (struct sockaddr *)&server_addr, sizeof(server_addr)))	{
		perror("bind");
		return -1;
	}
	// 监听客户端
	if (0 != listen(listenfd, LISTENQ)) {
		perror("listen");	
		return -1;
	}
	printf("Server listen: %s %d\n", inet_ntoa(server_addr.sin_addr), port_number);
	maxi = 0;
	while (1) {
		// 等待事件发生，返回活动的fd数量
		nfds = epoll_wait(epfd, event_list, EVENT_MAX_COUNT, TIMEOUT_MS);	

		for (i = 0;i < nfds; ++i) {
			if (event_list[i].data.fd == listenfd) { // 监听fd有事件产生
				client_addr_len = sizeof(struct sockaddr_in);
				connfd = accept(listenfd, (struct sockaddr *)&client_addr, &client_addr_len);
				if (connfd < 0) {
					printf("accept");
				}
				//
				char *str = inet_ntoa(client_addr.sin_addr);
				printf("accept a connection from %s\n", str);

				ev.data.fd = connfd;
				ev.events = EPOLLIN | EPOLLET;

				epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
			} else if (event_list[i].events & EPOLLIN) { // 客户端连接有数据到来
				printf("EPOLL IN\n");
				if ((sockfd = event_list[i].data.fd) < 0) {
					continue;
				}
				if ((n = read(sockfd, line_buf, MAX_LINE)) < 0) {
					if (errno == ECONNRESET) { // 客户端异常断开
						close(sockfd);
						event_list[i].data.fd = -1;
					} else {
						perror("read");
					}
				} else if (n == 0) { // 客户端正常关闭
					close(sockfd);
					event_list[i].data.fd = -1;
				}
				line_buf[n] = '\0';
				printf("recv : %s\n", line_buf);

				ev.data.fd = sockfd;
				ev.events = EPOLLOUT | EPOLLET;

				//epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
			} else if (event_list[i].events & EPOLLOUT) { // 有数据需要写
				sockfd = event_list[i].data.fd;
				write(sockfd, line_buf, n);

				ev.data.fd = sockfd;
				ev.events = EPOLLIN | EPOLLET;
				epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);
			}
		}
	}
    return 0;
}
/*

设置文件描述符为非阻塞状态

*/
void set_nonblocking(int sock)
{
	int opts;
	opts = fcntl(sock, F_GETFL);	
	if (opts < 0) {
		perror("fcntl");
		return;
	}
	opts = opts | O_NONBLOCK;
	if (fcntl(sock, F_SETFL, opts) < 0) {
		perror("fcntl");
	}	
}